﻿using System;
using System.Collections.Generic;
using System.Web.Mvc;
using Sencha.Serialization;

namespace Sencha.Direct
{
	public class JsonModelBinder : IModelBinder
	{
		private static readonly JsonSerializer DummySerializer = new JsonSerializer(typeof(object));

		public static readonly JsonModelBinder Default = new JsonModelBinder();

		public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
		{
			var directController = (controllerContext.Controller as DirectController);

			var modelType = bindingContext.ModelType;
			var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
			if (valueResult == null)
				return null;

			var value = Convert.ToString(valueResult.AttemptedValue);
			if (value == "null" || valueResult.AttemptedValue == null)
				return null;

			var isRouted = controllerContext.RequestContext.IsDirectRouted();
			var isFormRequest = string.IsNullOrEmpty(controllerContext.HttpContext.Request[Router.IncomingRequest.RequestFormAction]);

			if (isRouted && !isFormRequest) // routed request
			{
				// HINT: all paramteters is properly encoded

				if (modelType == typeof(string)) // string
					return new JsonStringReader(value, value.Length).ReadString();
				else if (modelType == typeof(char) && value.Length == 1) // char
					return value[0];
				else if (modelType.IsPrimitive && modelType != typeof(char)) // numbers, logical
					return TypeConvert.Convert(modelType, value, typeof(string), Json.DefaultFormatProvider);
				else if( modelType == typeof(DateTime))
					return DateTime.ParseExact(value, Json.DateTimeFormats, Json.DefaultFormatProvider, System.Globalization.DateTimeStyles.AssumeUniversal );
				else if( modelType == typeof(DateTimeOffset))
					return DateTimeOffset.ParseExact(value, Json.DateTimeFormats, Json.DefaultFormatProvider, System.Globalization.DateTimeStyles.AssumeUniversal );
			}
			else // plain, ajax, form request
			{
				// HINT: some parameters is may be unencoded

				var quoted = IsQuoted(value);

				if (modelType == typeof(string))
				{
					if (quoted)
						return JsonString.UnquoteAndUnescape(value);
					else
						return value;
				}
				else if (modelType == typeof(char) && value.Length == 1) // char
				{
					return value[0];
				}
				else if (value.Length == 0) // this is empty string and other that 'string' type is expected, so it's null
				{
					return null;
				}

				if (quoted)
					value = JsonString.UnquoteAndUnescape(value);

				if (modelType == typeof(DateTime))
					return DateTime.ParseExact(value, Json.DateTimeFormats, Json.DefaultFormatProvider, System.Globalization.DateTimeStyles.AssumeUniversal);
				if (modelType == typeof(DateTimeOffset))
					return DateTimeOffset.ParseExact(value, Json.DateTimeFormats, Json.DefaultFormatProvider, System.Globalization.DateTimeStyles.AssumeUniversal);

				return TypeConvert.Convert(modelType, value, typeof(string), Json.DefaultFormatProvider);
			}

			// complex type
			var serializer = (directController != null) ? directController.CreateSerializer(modelType) : new Sencha.Serialization.JsonSerializer(modelType);
			return serializer.ReadObject(value);
		}

		private bool IsQuoted(string literal)
		{
			if (literal.Length < 2)
				return false;

			var first = literal[0];
			var last = literal[literal.Length - 1];

			return (first == '"' && last == '"') || (first == '\'' && last == '\'');
		}
	}

	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
	public class JsonAttribute : CustomModelBinderAttribute
	{
		public override IModelBinder GetBinder()
		{
			return JsonModelBinder.Default;
		}
	}
}